
// OSDAppChooserDlg.cpp : implementation file
//
// 
// Copyright (c) Jason Sandys, 2009
//
// License: This code is released according to the 
// Microsoft Public License (Ms-PL) as documented at 
// http://osdappchooser.codeplex.com/license
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "OSDAppChooser.h"
#include "OSDAppChooserDlg.h"
#include "BalloonMsg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// COSDAppChooserDlg dialog




COSDAppChooserDlg::COSDAppChooserDlg(CjConfigMgrLogFile& logFile, ITSEnvClassPtr& tsEnv, CWnd* pParent /*=NULL*/)
	: CResizeDlg(COSDAppChooserDlg::IDD, pParent), m_logFile (logFile), m_tsEnv (tsEnv), m_cmdLine (::GetCommandLine())//, m_userSelect (false)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void COSDAppChooserDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDOK, m_ctrlOK);
	DDX_Control(pDX, IDC_APPSETPROMPT, m_ctrlAppSetPrompt);
	DDX_Control(pDX, IDC_APPSET, m_ctrlAppSets);
	DDX_Control(pDX, IDC_APPTREE, m_ctrlApps);
	DDX_Control(pDX, IDC_INFO, m_ctrlInfo);
}

BEGIN_MESSAGE_MAP(COSDAppChooserDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_CBN_SELCHANGE(IDC_APPSET, &COSDAppChooserDlg::OnCbnSelchangeAppset)
	ON_BN_CLICKED(IDOK, &COSDAppChooserDlg::OnBnClickedOk)
	ON_WM_SIZE()
	ON_WM_SIZING()
	ON_REGISTERED_MESSAGE(WM_XHTMLTREE_CHECKBOX_CLICKED, OnCheckbox)
	ON_REGISTERED_MESSAGE(WM_XHTMLTREE_CHECKBOX_CHECKED, OnCheckbox)
	ON_REGISTERED_MESSAGE(WM_XHTMLTREE_INIT_TOOLTIP, OnTooltipInit)
	ON_REGISTERED_MESSAGE(WM_XHTMLTREE_DISPLAY_TOOLTIP, OnTooltip)
END_MESSAGE_MAP()

// COSDAppChooserDlg message handlers

BOOL COSDAppChooserDlg::OnInitDialog()
{
	m_HeaderCtrl.Init(this);

	m_HeaderCtrl.MoveCtrls(this);

	CResizeDlg::OnInitDialog();

	AddControl(IDOK,				CST_REPOS,	CST_REPOS,	CST_REPOS,	CST_REPOS,	1);
	AddControl(IDC_APPSETPROMPT,	CST_RESIZE,	CST_RESIZE,	CST_NONE,	CST_NONE,	1);
	AddControl(IDC_APPSET,			CST_NONE,	CST_NONE,	CST_NONE,	CST_NONE,	1);
	AddControl(IDC_APPTREE,			CST_RESIZE,	CST_RESIZE,	CST_RESIZE,	CST_RESIZE,	1);
	AddControl(IDC_INFO,			CST_RESIZE,	CST_RESIZE,	CST_REPOS,	CST_REPOS,	1);
	AddControl(IDC_INFOICON,		CST_NONE,	CST_NONE,	CST_REPOS,	CST_REPOS,	1);
	AddControl(IDC_VERSION,			CST_NONE,	CST_NONE,	CST_REPOS,	CST_REPOS,	1);
	AddControl(IDC_DLGHEADER,		CST_RESIZE,	CST_RESIZE,	CST_NONE,	CST_NONE,	1);

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	m_imageList.Create (18, 18, ILC_COLOR32 | ILC_MASK, 5, 5);
	
	//Load the bitmap for the tree icons and add it to the tree
	CBitmap bmpTree;
	bmpTree.LoadBitmap (IDB_TREEIMAGES);

	m_imageList.Add (&bmpTree, 0x00FF00FF);

	//Hide the OK button because it is not valid unless an application set has been chosen
	m_ctrlOK.EnableWindow (false);

	//Set up the XHTMLTREE tree control
	m_ctrlApps.Initialize (true, true);
	m_ctrlApps.SetSmartCheckBox (true);
	m_ctrlApps.SetImageList (&m_imageList, TVSIL_NORMAL);

	SetInfoText (IDS_INFOTEXT1);

	try
	{
		CString strConfigFile = _T("OSDAppTreeConfig.xml");

		TRACE("Loading file...\n");
		
		//Get the default apset from the command-line if it exists
		if (m_cmdLine.HasKey (CMDLINE_CONFIGFILENAME))
			strConfigFile = m_cmdLine.GetVal (CMDLINE_CONFIGFILENAME);
		
		//Load the configuration file
		m_xmlConfig.LoadFile(strConfigFile);
		
		m_logFile.LogMsg (CjConfigMgrLogFile::Info, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGCONFIGFILESUCCESS, strConfigFile);
	
		m_HeaderCtrl.SetTitleText(m_xmlConfig.GetRootAttribute(TITLE_ATTRIBUTE, IDS_DEFAULTTITLE));
		m_HeaderCtrl.SetDescText(m_xmlConfig.GetRootAttribute(DESCRIPTION_ATTRIBUTE, IDS_DEFAULTDESCRIPTION));

		CString icon = m_xmlConfig.GetRootAttribute(ICON_ATTRIBUTE);

		if (icon.GetLength())
			m_hCustomIcon = (HICON)LoadImage(NULL, icon, IMAGE_ICON, 32, 32, LR_LOADFROMFILE);

		if (m_hCustomIcon)
			m_HeaderCtrl.SetIconHandle (m_hCustomIcon);
		else
			m_HeaderCtrl.SetIconHandle (m_hIcon);

		m_ctrlInfo.SetWindowText (m_xmlConfig.GetRootAttribute(INFO_ATTRIBUTE, IDS_DEFAULTINFO));

		LPCTSTR lpszDefaultAppSet = 0;
		
		//Get the default apset from the command-line if it exists
		if (m_cmdLine.HasKey (CMDLINE_DEFAULTAPPSET))
			lpszDefaultAppSet = m_cmdLine.GetVal (CMDLINE_DEFAULTAPPSET);

		PopulateAppSets (lpszDefaultAppSet);
	}
	catch (Error e)
	{
		TRACE1("Error loading file. %s\n", PCTSTR(e));
		m_logFile.LogMsg (CjConfigMgrLogFile::Error, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGERRORLOADINGCONFIG, PCTSTR(e));
		CDialog::EndDialog (1);
	}
	catch (_com_error ce)
	{
		TRACE1("Error loading file. %s\n", ce.ErrorMessage());
		m_logFile.LogMsg (CjConfigMgrLogFile::Error, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGERRORLOADINGCONFIG, ce.ErrorMessage());
		CDialog::EndDialog (1);
	}

	SetWindowPos(&this->wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

//************************************************************
/**
<summary>Simple helper function to set the info text</summary>

<param name='dwID'>(in)Resource ID of the string to use</param>
*/
//************************************************************
void COSDAppChooserDlg::SetInfoText (DWORD dwID, ...)
{
	if (dwID > 0)
	{
		va_list args;
		va_start(args, dwID);

		m_ctrlInfo.SetWindowText (FormatResourceString(dwID, &args));

		va_end(args);
	}

}


// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void COSDAppChooserDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR COSDAppChooserDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

//************************************************************
/**
<summary>Retrieves the application sets defined in the 
configuration file and adds them
to the combobox on the dialog</summary>

<param name='lpszDefAppSet'>(in)Default AppSet specified on the 
command-line, NULL if none specified</param>
*/
//************************************************************
void COSDAppChooserDlg::PopulateAppSets(LPCTSTR lpszDefAppSet)
{
	CString strPrompt = _T("");
	CStringList strlstAppSets;
	CString strAppSet = _T("");
	CString strAllAppSets = _T("");

	//Get the prompt for the application set and set the static
	//control for the prompt
	m_ctrlAppSetPrompt.SetWindowText (m_xmlConfig.GetAppSetPrompt ());

	//Get a collection of the valid app sets from the configuration file
	m_xmlConfig.GetAppSets (strlstAppSets, lpszDefAppSet);
	
	//Loop through the app sets return and populate the comboxbox with them
	for (POSITION pos = strlstAppSets.GetHeadPosition (); pos != NULL; )
	{
		strAppSet = strlstAppSets.GetNext (pos);
		m_ctrlAppSets.AddString (strAppSet);
		strAllAppSets += strAppSet;

		//Build a string for the log
		if (pos != NULL)
			strAllAppSets += _T(", ");
	}

	//If a valid default AppSet was specified on the command-line, select it, disable the combobox, and populate the tree
	if (lpszDefAppSet)
	{
		m_ctrlAppSets.SelectString (0, lpszDefAppSet);
		m_ctrlAppSets.EnableWindow (false);
		SelectAppSet (lpszDefAppSet);
		m_logFile.LogMsg (CjConfigMgrLogFile::Info, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGDEFAPPSET, (LPCTSTR)lpszDefAppSet);
	}

	m_logFile.LogMsg (CjConfigMgrLogFile::Info, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGAPPSETSLOADED, (LPCTSTR)strAllAppSets);

}

//************************************************************
/**
<summary>Add an item to the application tree</summary>

<param name='strName'>(in)Label of the item to add</param>
<param name='hParent'>(in)The parent item in the tree</param>
<param name='bDefault'>(in)Is the item checked by default</param>
<param name='bMandatory'>(in)Is the item mandatory, this will grey
the item, check it, and disable it</param>
<param name='nIndex'>(in)The index of the item in the configuration
file</param>
<returns>A handle to the item added to the tree</returns>
*/
//************************************************************
HTREEITEM COSDAppChooserDlg::AddItem(PCTSTR pName, HTREEITEM hParent, bool bDefault, bool bMandatory, PCTSTR appID)
{
	HTREEITEM hItem = 0;
	short nIconIndex = TREEICON_GROUP;
	CString text, icon;
	
	//If the item to add is not a group, set the icon index
	if (appID != 0)
		nIconIndex = TREEICON_APP;

	//If the item is mandatory, adjust the icon index
	if (bMandatory)
		nIconIndex++;

	//Add the item to the tree
	hItem = m_ctrlApps.InsertItem (pName, nIconIndex, nIconIndex, hParent);

	if (appID && hItem)
	{
		APPDATA* pAppData = new APPDATA;
		pAppData->id = appID;
		pAppData->mandatory = bMandatory;

		m_xmlConfig.FindAppRequirments(appID, pAppData->reqAppsList);
		m_xmlConfig.FindAppDependants(appID, pAppData->depAppsList);

		m_appData.AddTail (pAppData);
		m_appTreeMap.SetAt(appID, hItem);

		m_ctrlApps.SetItemData (hItem, (DWORD_PTR)pAppData);

		m_xmlConfig.GetAppTooltipInfo(appID, text, icon);

		SetToolTip (hItem, pName, icon, text);
	}

	if (hItem && (bMandatory || bDefault))
		m_ctrlApps.SetCheck (hItem, true);

	if (hItem && bMandatory)
		m_ctrlApps.EnableItem (hItem, false);

	return hItem;

}

//************************************************************
/**
<summary>Handle a changed combobox</summary>
*/
//************************************************************
void COSDAppChooserDlg::OnCbnSelchangeAppset()
{
	CString strAppSet;

	if (m_ctrlAppSets.GetCurSel() != CB_ERR)
	{
		TRACE ("AppSet Changing\n");

		//Get the app set chosen in the combobox
		m_ctrlAppSets.GetLBText (m_ctrlAppSets.GetCurSel(), strAppSet);

		TRACE1 ("AppSet Changed: %s\n", strAppSet);

		SelectAppSet (strAppSet);
	}
}

//************************************************************
/**
<summary>Populate the application tree based upon the AppSet 
passed in</summary>

<param name='lpszAppSet'>(in)The AppSet to use to populate the 
tree</param>
*/
//************************************************************
void COSDAppChooserDlg::SelectAppSet(LPCTSTR lpszAppSet)
{
	if (lpszAppSet == 0)
		return;
	
	//Enable the OK button because an application set has been chosen
	m_ctrlOK.EnableWindow (true);

	while (!m_appData.IsEmpty())
	{
		delete m_appData.GetHead ();
		m_appData.RemoveHead ();
	}

	m_appTreeMap.RemoveAll();

	try
	{	
		//Clear all of the apps currently displayed
		m_ctrlApps.DeleteAllItems ();
		
		//Get all of the apps for the currently chosen app set
		//Pass a pointer to this dialog so that getApps can call back and add apps and groups
		m_xmlConfig.GetApps (this, lpszAppSet);

		_TCHAR* lpszCheckedCount = new _TCHAR[MAX_STRING_LENGTH];
		if (_itot_s (m_ctrlApps.GetLeafCheckedCount(), lpszCheckedCount, MAX_STRING_LENGTH, 10) == 0 )
			SetInfoText (IDS_INFOTEXT2, lpszCheckedCount);

        delete lpszCheckedCount;

	}
	catch (Error e)
	{
		TRACE1("Error selecting AppSet. %s\n", PCTSTR(e));
		m_logFile.LogMsg (CjConfigMgrLogFile::Error, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGERRORCHANGINGAPPSETS, lpszAppSet, PCTSTR(e));
		CDialog::EndDialog (1);
	}
	catch (_com_error ce)
	{
		TRACE1("Error selecting AppSet. %s\n", ce.ErrorMessage());
		m_logFile.LogMsg (CjConfigMgrLogFile::Error, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGERRORCHANGINGAPPSETS, lpszAppSet, ce.ErrorMessage());
		CDialog::EndDialog (1);
	}
}

//************************************************************
/**
<summary>Handle the OK button click event</summary>
*/
//************************************************************
void COSDAppChooserDlg::OnBnClickedOk()
{
	long itemIndex = -1;									//Used to store the index of an aplication as retrieved 
															//	from the tree and originally stored when the item was created
	APPDATA* pAppData;
	
	
	IndexSet indexes;										//An STL set to store the indexes of the checked items. STL sets 
															//	automaticaly sort the items which ensures that we retrieve apps 
															//	from the configuration file in the order that they are defined
	CString strAppSet;										//Used to retrieve the AppSet from the combo box
	//IndexSet reqIndexes;

	//Get the currenlty select AppSet from the Combo Box
	m_ctrlAppSets.GetLBText (m_ctrlAppSets.GetCurSel(), strAppSet);

	m_logFile.LogMsg (CjConfigMgrLogFile::Info, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGAPPSETCHOSEN, strAppSet);

	HTREEITEM hItem = m_ctrlApps.GetFirstCheckedItem ();	//Handle to the first tree item checked

	//Loop through the selected apps in the tree
	while (hItem != NULL)
	{
		pAppData = (APPDATA*)m_ctrlApps.GetItemData (hItem);

		if (pAppData)
		{
			itemIndex = m_xmlConfig.GetAppIndex(pAppData->id);
			TRACE3 ("%d -> %s(%s)\n", itemIndex, pAppData->id, m_ctrlApps.GetItemText (hItem));
			
			indexes.insert(itemIndex);

			POSITION pos = pAppData->reqAppsList.GetHeadPosition();

			CString appID;

			while (pos)
			{
				appID = pAppData->reqAppsList.GetNext(pos);
				itemIndex = m_xmlConfig.GetAppIndex(appID);
				TRACE2 ("%d -> %s\n", itemIndex, appID);
				indexes.insert(itemIndex);
			}
		}

		hItem = m_ctrlApps.GetNextCheckedItem (hItem);
	}

	//Used to store various strings corresponding to their names
	CString strPkgID, strProgID, strVarName, strSeqVarName, strVarValue, strMsg, reqPkgIDs;
	int nVarIndex = 1;

	//Get the TS variable name to use from the command-line
	bool bVarExists = m_cmdLine.HasKey (CMDLINE_TSVARNAME);
	if (bVarExists)
		strVarName = m_cmdLine.GetVal (CMDLINE_TSVARNAME);
	
	//Use the default TS variable name because one was not specified on the command-line or was blank
	if (!bVarExists || strVarName.StringLength (strVarName) == 0)
		strVarName = DEFAULT_TSVARNAME;

	//Loop through the indexes retrieved and build the TS variables
	for ( IndexSetInterator iter = indexes.begin(); iter != indexes.end(); ++iter)
	{
		if (m_xmlConfig.GetAppInfo (*iter, strPkgID, strProgID))
		{
			strSeqVarName.Format (_T ("%s%03d"), strVarName, nVarIndex);
			strVarValue.Format (_T("%s:%s"), strPkgID, strProgID);
			strMsg.Format (_T ("%s = %s"), strSeqVarName, strVarValue);

			if (m_tsEnv)
			{
				//Set the TS variable using the TSEnvironment COM object
				m_tsEnv->Value[strSeqVarName.AllocSysString()] = strVarValue.AllocSysString();
			}
			
			nVarIndex++;
			
			m_logFile.LogMsg (CjConfigMgrLogFile::Info, AfxGetThread()->m_nThreadID, __WFILE__, __LINE__, IDS_MSGTSVARSET, strMsg);

		}
	}
	//Call the default handler to close the dialog
	OnOK();

}

void COSDAppChooserDlg::OnSize(UINT nType, int cx, int cy)
{
	CResizeDlg::OnSize(nType, cx, cy);

}

void COSDAppChooserDlg::OnSizing(UINT fwSide, LPRECT pRect)
{
	CResizeDlg::OnSizing(fwSide, pRect);

	if(pRect->right - pRect->left < MINWIDTH) 
	{
		if((fwSide == WMSZ_BOTTOMLEFT) || (fwSide == WMSZ_LEFT) || (fwSide == WMSZ_TOPLEFT))
			pRect->left = pRect->right - MINWIDTH; 
		else 
			pRect->right = pRect->left + MINWIDTH; 
	} 
	
	if(pRect->bottom - pRect->top < MINHEIGHT) 
	{ 
		if((fwSide == WMSZ_TOP) || (fwSide == WMSZ_TOPLEFT) || (fwSide == WMSZ_TOPRIGHT)) 
			pRect->top = pRect->bottom - MINHEIGHT; 
		else 
			pRect->bottom = pRect->top + MINHEIGHT; 
	}


}

//************************************************************
/**
<summary>Set the info text every time a checkbox is clicked
</summary>
*/
//************************************************************
LRESULT COSDAppChooserDlg::OnCheckbox(WPARAM wParam, LPARAM lParam)
{
	_TCHAR* lpszCheckedCount = new _TCHAR[MAX_STRING_LENGTH];
	if (_itot_s (m_ctrlApps.GetLeafCheckedCount(), lpszCheckedCount, MAX_STRING_LENGTH, 10) == 0 )
		SetInfoText (IDS_INFOTEXT2, lpszCheckedCount);

    delete lpszCheckedCount;

	if (wParam)
	{
		XHTMLTREEMSGDATA* pTreeData = (XHTMLTREEMSGDATA*)wParam;
		BOOL check = (BOOL)lParam;

		APPDATA* pAppData = (APPDATA*)m_ctrlApps.GetItemData (pTreeData->hItem);

		if (pAppData)
		{
			POSITION pos = pAppData->reqAppsList.GetHeadPosition();

			CString appID;
			HTREEITEM hRequiredApp;
			APPDATA* pRequiredAppData;

			while (pos)
			{
				appID = pAppData->reqAppsList.GetNext(pos);

				if (m_appTreeMap.Lookup(appID, hRequiredApp))
				{
					pRequiredAppData = (APPDATA*)m_ctrlApps.GetItemData (hRequiredApp);

					if (check)
					{
						if (pRequiredAppData->currentDepApps.size() == 0)
							pRequiredAppData->prevSelect = m_ctrlApps.GetCheck(hRequiredApp);

						pRequiredAppData->currentDepApps.insert(pAppData->id);

						if (!pRequiredAppData->mandatory)
						{
							m_ctrlApps.SetCheck(hRequiredApp);
							m_ctrlApps.EnableItem(hRequiredApp, FALSE);
						
							m_ctrlApps.SetItemImage(hRequiredApp, TREEICON_LINKEDAPP, TREEICON_LINKEDAPP);
						}

					}
					else
					{
						pRequiredAppData->currentDepApps.erase(pAppData->id);

						if (!pRequiredAppData->mandatory && pRequiredAppData->currentDepApps.size() == 0)
						{
							m_ctrlApps.EnableItem(hRequiredApp, TRUE);
							m_ctrlApps.SetItemImage(hRequiredApp, TREEICON_APP, TREEICON_APP);

							if (!pRequiredAppData->prevSelect)
								m_ctrlApps.SetCheck(hRequiredApp, FALSE);

						}
					}
				}
			}
		}
	}

	return 0;

}

LRESULT COSDAppChooserDlg::OnTooltip(WPARAM wParam, LPARAM lParam)
{

	SetToolTip(((XHTMLTREEMSGDATA*)wParam)->hItem);
	
	return 0;
}

LRESULT COSDAppChooserDlg::OnTooltipInit(WPARAM wParam, LPARAM lParam)
{

	CPPToolTip* tip = (CPPToolTip*)lParam;

	CString css = _T("h2 { color:RoyalBlue; font-size:18px; font-weight:bold; font-family:Tahoma; } h3 { color:black; font-size:13px; font-family:Tahoma; } h4 { color:black; font-size:12px; font-weight:bold; font-family:Tahoma; } h5 { color:black; font-size:12px; font-family:Tahoma; }");

	tip->SetCssStyles (css);

	tip->SetColorBk(RGB(250,250,255), RGB(200,200,220));

	tip->SetEffectBk(CPPDrawManager::EFFECT_SOFTBUMP);

	tip->SetDelayTime(PPTOOLTIP_TIME_AUTOPOP, 15000);

	return 0;
}


BOOL COSDAppChooserDlg::DestroyWindow()
{
	while (!m_appData.IsEmpty())
	{
		delete m_appData.GetHead ();
		m_appData.RemoveHead ();
	}

	return CResizeDlg::DestroyWindow();
}

void COSDAppChooserDlg::SetToolTip(HTREEITEM hItem, PCTSTR pAppName, PCTSTR pIcon, PCTSTR pAppInfo)
{

	APPDATA* pAppData = (APPDATA*)m_ctrlApps.GetItemData(hItem);

	if (pAppData)
	{
		CString tip = m_ctrlApps.GetItemNote(hItem);
	
		if (pAppName != 0)
		{
			tip = _T("<table><tr><td><icon ");
			
			if (pIcon != 0 && _tcslen(pIcon) != 0)
			{
				tip += _T("file=\"");
				tip += pIcon;
				tip += _T("\" ");
			}
			else
			{
				tip += _T("idres=\"");

				CString appIcon;
				appIcon.Format (_T("%d"), IDI_APP);

				tip += appIcon;
			}

			tip += _T("\"/></td><td align=\"left\" valign=\"middle\"><h2>");
			tip += pAppName;			
			tip += _T("</h2></td></tr></table><hr color=lightsteelblue>");
			
			if (pAppInfo != 0 && _tcslen(pAppInfo) != 0)
			{
				tip += _T("<br><h3>");
				tip += pAppInfo;
				tip += _T("</h3><br>");
			}
		}
		else if (!pAppData->toolTipSet)
		{
			AddTooltipApps(tip, pAppData->reqAppsList, _T("Depends On"));
			AddTooltipApps(tip, pAppData->depAppsList, _T("Depended On By"));

			pAppData->toolTipSet = true;
		}

		else
			return;

		m_ctrlApps.SetItemNote (hItem, tip);

	}

}

void COSDAppChooserDlg::AddTooltipApps(CString& tooltip, const CStringList& appList, PCTSTR pHeading)
{
	if (!appList.IsEmpty())
	{
		POSITION pos = appList.GetHeadPosition();
		HTREEITEM hDepApp;
		CString depAppID;
		CString depTip;
		int depCount = 0;

		while (pos)
		{
			depAppID = appList.GetAt(pos);
			if (m_appTreeMap.Lookup(depAppID, hDepApp))
			{
				depTip += _T("- ");
				depTip += m_ctrlApps.GetItemText(hDepApp);
				//depTip += depAppID;
				depTip += _T("<br>");

				depCount++;
			}

			appList.GetNext(pos);
		}

		if (depCount > 0)
		{
			tooltip += _T("<br><h4>");
			tooltip += pHeading;
			tooltip += _T("</h4><br><h5>");
			tooltip += depTip;
			tooltip += _T("</h5>");
		}
	}
}
